/********************************************************************************************************
 * @file    spi.c
 *
 * @brief   This is the source file for B91
 *
 * @author  Driver Group
 * @date    2019
 *
 * @par     Copyright (c) 2019, Telink Semiconductor (Shanghai) Co., Ltd. ("TELINK")
 *
 *          Licensed under the Apache License, Version 2.0 (the "License");
 *          you may not use this file except in compliance with the License.
 *          You may obtain a copy of the License at
 *
 *              http://www.apache.org/licenses/LICENSE-2.0
 *
 *          Unless required by applicable law or agreed to in writing, software
 *          distributed under the License is distributed on an "AS IS" BASIS,
 *          WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *          See the License for the specific language governing permissions and
 *          limitations under the License.
 *
 *******************************************************************************************************/
#include "spi.h"


static unsigned char s_hspi_tx_dma_chn;
static unsigned char s_hspi_rx_dma_chn;
static unsigned char s_pspi_tx_dma_chn;
static unsigned char s_pspi_rx_dma_chn;

dma_config_t hspi_tx_dma_config = {
    .dst_req_sel    = DMA_REQ_SPI_AHB_TX,//tx req
    .src_req_sel    = 0,
    .dst_addr_ctrl  = DMA_ADDR_FIX,
    .src_addr_ctrl  = DMA_ADDR_INCREMENT,//increment
    .dstmode        = DMA_HANDSHAKE_MODE,//handshake
    .srcmode        = DMA_NORMAL_MODE,
    .dstwidth       = DMA_CTR_WORD_WIDTH,//must word
    .srcwidth       = DMA_CTR_WORD_WIDTH,//must word
    .src_burst_size = 0,//must 0
    .read_num_en    = 0,
    .priority       = 0,
    .write_num_en   = 0,
    .auto_en        = 0,//must 0
};
 dma_config_t hspi_rx_dma_config = {
    .dst_req_sel   = 0,//tx req
    .src_req_sel    = DMA_REQ_SPI_AHB_RX,
    .dst_addr_ctrl  = DMA_ADDR_INCREMENT,
    .src_addr_ctrl  = DMA_ADDR_FIX,
    .dstmode        = DMA_NORMAL_MODE,
    .srcmode        = DMA_HANDSHAKE_MODE,
    .dstwidth       = DMA_CTR_WORD_WIDTH,//must word
    .srcwidth       = DMA_CTR_WORD_WIDTH,////must word
    .src_burst_size = 0,
    .read_num_en    = 0,
    .priority       = 0,
    .write_num_en   = 0,
    .auto_en        = 0,//must 0
};

dma_config_t pspi_tx_dma_config = {
    .dst_req_sel    = DMA_REQ_SPI_APB_TX,//tx req
    .src_req_sel    = 0,
    .dst_addr_ctrl  = DMA_ADDR_FIX,
    .src_addr_ctrl  = DMA_ADDR_INCREMENT,//increment
    .dstmode        = DMA_HANDSHAKE_MODE,//handshake
    .srcmode        = DMA_NORMAL_MODE,
    .dstwidth       = DMA_CTR_WORD_WIDTH,//must word
    .srcwidth       = DMA_CTR_WORD_WIDTH,//must word
    .src_burst_size = 0,//must 0
    .read_num_en    = 0,
    .priority       = 0,
    .write_num_en   = 0,
    .auto_en        = 0,//must 0
};

dma_config_t pspi_rx_dma_config = {
    .dst_req_sel    = 0,//tx req
    .src_req_sel    = DMA_REQ_SPI_APB_RX,
    .dst_addr_ctrl  = DMA_ADDR_INCREMENT,
    .src_addr_ctrl  = DMA_ADDR_FIX,
    .dstmode        = DMA_NORMAL_MODE,
    .srcmode        = DMA_HANDSHAKE_MODE,
    .dstwidth       = DMA_CTR_WORD_WIDTH,//must word
    .srcwidth       = DMA_CTR_WORD_WIDTH,////must word
    .src_burst_size = 0,
    .read_num_en    = 0,
    .priority       = 0,
    .write_num_en   = 0,
    .auto_en        = 0,//must 0
};

/**
 * @brief      This function selects  pin  for hspi master or slave mode.
 * @param[in]  pin  - the selected pin.
 * @return     none
 */
void hspi_set_pin_mux(hspi_pin_def_e pin)
{
    if (pin != HSPI_NONE_PIN)
    {
        unsigned char val = 0;
        unsigned char start_bit = (BIT_LOW_BIT(pin & 0xff) % 4) << 1;
        unsigned char mask = (unsigned char)~BIT_RNG(start_bit, start_bit + 1);

        if ((pin == HSPI_CLK_PB4_PIN) || (pin == HSPI_CSN_PB6_PIN) || (pin == HSPI_MOSI_IO0_PB3_PIN) || (pin == HSPI_MISO_IO1_PB2_PIN) || (pin == HSPI_WP_IO2_PB1_PIN) || (pin == HSPI_HOLD_IO3_PB0_PIN))
        {
            val = 0;//function 0
        }
        else if ((pin == HSPI_CLK_PA2_PIN) || (pin == HSPI_CSN_PA1_PIN) || (pin == HSPI_MOSI_IO0_PA4_PIN) || (pin == HSPI_MISO_IO1_PA3_PIN))
        {
            val = 2 << (start_bit);//function 2
            reg_gpio_pad_mul_sel |= BIT(1);
        }
        /**
         * When configuring the mux pin of SPI, pull up the CSN configuration.
         * The chip SPI defaults to the master, and when the slave configures the pin as CSN, since the CSN is floating and in the input state,
         * an end interrupt may be generated by external influences.
         * Added by jiarong.ji on July 22, 2021
         */
        if((pin == HSPI_CSN_PB6_PIN) || (pin == HSPI_CSN_PA1_PIN)){
            gpio_set_up_down_res(pin, GPIO_PIN_PULLUP_10K);
        }
        /**
         * Move the operation of gpio_input_en() to the first.
         * Added by jiarong.ji on July 22, 2021
         */
        gpio_input_en(pin);
        reg_gpio_func_mux(pin) = (reg_gpio_func_mux(pin) & mask) | val;
        gpio_function_dis(pin);
    }
}

/**
 * @brief       This function enable hspi csn pin.
 * @param[in]   pin - the csn pin.
 * @return      none
 */
void hspi_cs_pin_en(hspi_csn_pin_def_e pin)
{
    hspi_set_pin_mux(pin);
}

/**
 * @brief       This function disable hspi csn pin.
 * @param[in]   pin - the csn pin.
 * @return      none
 */
void hspi_cs_pin_dis(hspi_csn_pin_def_e pin)
{
    /**
     * Bug fix: Move the operation of gpio_function_en to the end of the function.
     *      changed by chaofan.20210301.
     */
    gpio_output_en(pin);
    gpio_input_dis(pin);
    gpio_set_high_level(pin);
    gpio_function_en(pin);
}

/**
 * @brief       This function change hspi csn pin.
 * @param[in]   current_csn_pin - the current csn pin.
 * @param[in]   next_csn_pin - the next csn pin.
 * @return      none.
 */
void hspi_change_csn_pin(hspi_csn_pin_def_e current_csn_pin, hspi_csn_pin_def_e next_csn_pin)
{
    hspi_cs_pin_dis(current_csn_pin);
    hspi_cs_pin_en(next_csn_pin);
}

/**
 * @brief      This function selects  pin  for pspi master or slave mode.
 * @param[in]  pin  - the selected pin.
 * @return     none
 */
void pspi_set_pin_mux(pspi_pin_def_e pin)
{
    if (pin!= PSPI_NONE_PIN)
    {
        unsigned char val = 0;
        unsigned char start_bit = (BIT_LOW_BIT(pin & 0xff) %4) << 1;
        unsigned char mask  = (unsigned char)~BIT_RNG(start_bit, start_bit + 1);
        if ((pin == PSPI_CLK_PC5_PIN) || (pin == PSPI_CSN_PC4_PIN) || (pin == PSPI_MOSI_IO0_PC7_PIN) || (pin == PSPI_MISO_IO1_PC6_PIN))
        {
            val = 0;//function 0
        }

        else if ((pin == PSPI_CLK_PB5_PIN) || (pin == PSPI_CLK_PD1_PIN) || (pin == PSPI_CSN_PC0_PIN) || (pin == PSPI_CSN_PD0_PIN) || (pin == PSPI_MOSI_IO0_PB7_PIN) || (pin == PSPI_MOSI_IO0_PD3_PIN) || (pin == PSPI_MISO_IO1_PB6_PIN) || (pin == PSPI_MISO_IO1_PD2_PIN))
        {
            val = 1 << (start_bit);//function 1
        }
        /**
         * When configuring the mux pin of SPI, pull up the CSN configuration.
         * The chip SPI defaults to the master, and when the slave configures the pin as CSN, since the CSN is floating and in the input state,
         * an end interrupt may be generated by external influences.
         * Added by jiarong.ji on July 22, 2021
         */
        if((pin == PSPI_CSN_PC4_PIN) || (pin == PSPI_CSN_PC0_PIN) || (pin == PSPI_CSN_PD0_PIN)){
            gpio_set_up_down_res(pin, GPIO_PIN_PULLUP_10K);
        }
        /**
         * Move the operation of gpio_input_en() to the first.
         * Added by jiarong.ji on July 22, 2021
         */
        gpio_input_en(pin);
        reg_gpio_func_mux(pin) = (reg_gpio_func_mux(pin) & mask) | val;
        gpio_function_dis(pin);
    }
}
/**
 * @brief       This function enable pspi csn pin.
 * @param[in]   pin - the csn pin.
 * @return      none
 */
void pspi_cs_pin_en(pspi_csn_pin_def_e pin)
{
    pspi_set_pin_mux(pin);
}

/**
 * @brief       This function disable pspi csn pin.
 * @param[in]   pin - the csn pin.
 * @return      none
 */
void pspi_cs_pin_dis(pspi_csn_pin_def_e pin)
{
    /**
     * Bug fix: Move the operation of gpio_function_en to the end of the function.
     *      changed by chaofan.20210301.
     */
    gpio_output_en(pin);
    gpio_input_dis(pin);
    gpio_set_high_level(pin);
    gpio_function_en(pin);
}

/**
 * @brief       This function change pspi csn pin.
 * @param[in]   current_csn_pin - the current csn pin.
 * @param[in]   next_csn_pin - the next csn pin.
 * @return      none.
 */
void pspi_change_csn_pin(pspi_csn_pin_def_e current_csn_pin, pspi_csn_pin_def_e next_csn_pin)
{

    pspi_cs_pin_dis(current_csn_pin);
    pspi_cs_pin_en(next_csn_pin);
}
/**
 * @brief     This function configures hspi pin.
 * @param[in] config - the pointer of pin config struct.
 * @return    none
 */
void hspi_set_pin(hspi_pin_config_t *config)
{
    hspi_set_pin_mux(config->hspi_csn_pin);
    hspi_set_pin_mux(config->hspi_clk_pin);
    hspi_set_pin_mux(config->hspi_mosi_io0_pin);
    hspi_set_pin_mux(config->hspi_miso_io1_pin);
    hspi_set_pin_mux(config->hspi_wp_io2_pin);
    hspi_set_pin_mux(config->hspi_hold_io3_pin);
}

/**
 * @brief       This function configures pspi pin.
 * @param[in]   config - the pointer of pin config struct.
 * @return      none
 */
void pspi_set_pin(pspi_pin_config_t *config)
{
    pspi_set_pin_mux(config->pspi_clk_pin);
    pspi_set_pin_mux(config->pspi_csn_pin);
    pspi_set_pin_mux(config->pspi_mosi_io0_pin);
    pspi_set_pin_mux(config->pspi_miso_io1_pin);
}

/**
 * @brief   This function selects  pin  for hspi master or slave.
 * @return  none
 */
void spi_slave_set_pin(void)
{
    /**
     * Bug fix: Replace the incorrectly used reg_gpio_pb_fuc_l with reg_gpio_pa_fuc_l.
     *      changed by chaofan.20210301.
     */
    reg_gpio_pa_fuc_l = (reg_gpio_pa_fuc_l & 0x03);//set PA1 as csn,PA2 as clk,PA3 as mosi_io0,
    reg_gpio_pa_fuc_h = (reg_gpio_pa_fuc_l & 0xfc);//set PA4 slave miso_io1
    gpio_function_dis(GPIO_PA1 | GPIO_PA2 | GPIO_PA3 | GPIO_PA4);
    gpio_input_en(GPIO_PA1 | GPIO_PA2 | GPIO_PA3 | GPIO_PA4);
}

/**
 * @brief       This function configures the clock and working mode for SPI interface.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   div_clock   - the division factor for SPI module.
 *              spi_clock_out = ahb_clock / ((div_clock+1)*2)
 * @param[in]    mode       - the selected working mode of SPI module.
 *              bit5:CPHA-Clock Polarity ; bit6:CPOL:CPHA-Clock Phase
 *              MODE0:  CPOL = 0, CPHA =0;
 *              MODE1:  CPOL = 0, CPHA =1;
 *              MODE2:  CPOL = 1, CPHA =0;
 *              MODE3:  CPOL = 1, CPHA =1;
 * @return      none
 */
void spi_master_init(spi_sel_e spi_sel, unsigned char div_clock, spi_mode_type_e mode)
{
     reg_spi_mode1(spi_sel) = div_clock;
     reg_spi_mode0(spi_sel) |= FLD_SPI_MASTER_MODE;//master
     reg_spi_mode0(spi_sel) = ((reg_spi_mode0(spi_sel) & (~FLD_SPI_MODE_WORK_MODE)) | (mode << 5));// select SPI mode, support four modes
}


/**
 * @brief       This function configures the clock and working mode for SPI interface.
 * @param[in]   spi_sel - the spi module.
 * @param[in]   mode    - the selected working mode of SPI module.
 *              bit5:CPHA-Clock Polarity ; bit6:CPOL:CPHA-Clock Phase
 *              MODE0:  CPOL = 0, CPHA =0;
 *              MODE1:  CPOL = 0, CPHA =1;
 *              MODE2:  CPOL = 1, CPHA =0;
 *              MODE3:  CPOL = 1, CPHA =1;
 * @return      none
 * @note        spi_clock_in  (spi_slave_clock frequency)/3
 */
void spi_slave_init(spi_sel_e spi_sel, spi_mode_type_e mode)
{
     reg_spi_mode0(spi_sel) &= (~FLD_SPI_MASTER_MODE);//slave
     reg_spi_mode0(spi_sel) = ((reg_spi_mode0(spi_sel) & (~FLD_SPI_MODE_WORK_MODE)) | (mode << 5));// select SPI mode, support four modes
}

/**
 * @brief       This function servers to set dummy cycle cnt.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   dummy_cnt   - the cnt of dummy clock.
 * @return      none
 */
void spi_set_dummy_cnt(spi_sel_e spi_sel, unsigned char dummy_cnt)
{
    reg_spi_trans0(spi_sel) = ((reg_spi_trans0(spi_sel) & (~FLD_SPI_DUMMY_CNT)) | ((dummy_cnt - 1) & FLD_SPI_DUMMY_CNT));
}

/**
 * @brief       This function servers to set spi transfer mode.
 * @param[in]   spi_sel - the spi module.
 * @param[in]   mode    - transfer mode.
 * @return      none
 */
void spi_set_transmode(spi_sel_e spi_sel, spi_tans_mode_e mode)
{
    reg_spi_trans0(spi_sel) = ((reg_spi_trans0(spi_sel) & (~FLD_SPI_TRANSMODE)) | ((mode & 0xf) << 4));
}

/**
 * @brief       This function servers to set normal mode.
 * @param[in]   spi_sel     - the spi module.
 * @return      none
 */
void spi_set_normal_mode(spi_sel_e spi_sel)
{
    spi_dual_mode_dis(spi_sel);
    spi_3line_mode_dis(spi_sel);
    if (HSPI_MODULE == spi_sel)
    {
        hspi_quad_mode_dis(spi_sel);
    }
}

/**
 * @brief       This function servers to set dual mode.
 * @param[in]   spi_sel     - the spi module.
 * @return      none
 */
void spi_set_dual_mode(spi_sel_e spi_sel)
{
    spi_dual_mode_en(spi_sel);//quad  precede over dual
    spi_3line_mode_dis(spi_sel);
    if (HSPI_MODULE == spi_sel)
    {
        hspi_quad_mode_dis(spi_sel);
    }
}

/**
 * @brief   This function servers to set quad mode.
 * @return  none
 */
void hspi_set_quad_mode()
{
    hspi_quad_mode_en();
    spi_dual_mode_dis(HSPI_MODULE);
    spi_3line_mode_dis(HSPI_MODULE);
}

/**
 * @brief       This function servers to set 3line mode.
 * @param[in]   spi_sel     - the spi module.
 * @return      none
 */
void spi_set_3line_mode(spi_sel_e spi_sel)
{
    /*must disable dual and quad*/
    spi_3line_mode_en(spi_sel);
    spi_dual_mode_dis(spi_sel);
    if (HSPI_MODULE == spi_sel)
    {
        hspi_quad_mode_dis(spi_sel);
    }
}

/**
 * @brief       This function servers to set hspi io  mode.
 * @param[in]   spi_sel - the spi module.
 * @param[in]   mode    - single/dual/quad /3line.
 * @return      none
  */
void spi_set_io_mode(spi_sel_e spi_sel, spi_io_mode_e mode)
{
    switch (mode)
    {
        case SPI_SINGLE_MODE:
            spi_set_normal_mode(spi_sel);
            break;
        case SPI_DUAL_MODE:
            spi_set_dual_mode(spi_sel);
            break;
        case HSPI_QUAD_MODE:
            hspi_set_quad_mode();
            break;
        case SPI_3_LINE_MODE:
            spi_set_3line_mode(spi_sel);
            break;
    }
}

/**
 * @brief       This function servers to config normal mode.
 * @param[in]   spi_sel - the spi module.
 * @param[in]   mode    - normal ,mode 3line.
 * @return      none
 */
void spi_master_config(spi_sel_e spi_sel, spi_normal_3line_mode_e mode)
{
    spi_cmd_dis(spi_sel);
    if (HSPI_MODULE == spi_sel)
    {
        hspi_addr_dis();
    }
    spi_set_io_mode(spi_sel, mode);
}

/**
 * @brief       This function servers to config hspi special mode.
 * @param[in]   config  - the pointer of pin special config struct.
 * @return      none
 */
void hspi_master_config_plus(hspi_config_t *config)
{
    spi_set_io_mode(HSPI_MODULE, config->hspi_io_mode);
    hspi_set_addr_len(config->hspi_addr_len);
    spi_set_dummy_cnt(HSPI_MODULE, config->hspi_dummy_cnt);

    if (1 == config->hspi_cmd_en)
    {
        spi_cmd_en(HSPI_MODULE);
    }
    else if (0 == config->hspi_cmd_en)
    {
        spi_cmd_dis(HSPI_MODULE);
    }

    if (1 == config->hspi_cmd_fmt_en)
    {
        hspi_cmd_fmt_en();
    }
    else if (0 == config->hspi_cmd_fmt_en)
    {
       hspi_cmd_fmt_dis();
    }

    if (1 == config->hspi_addr_en)
    {
        hspi_addr_en();
    }
    else if (0 == config->hspi_addr_en)
    {
        hspi_addr_dis();
    }

    if (1 == config->hspi_addr_fmt_en)
    {
        hspi_addr_fmt_en();
    }
    else if (0 == config->hspi_addr_fmt_en)
    {
        hspi_addr_fmt_dis();
    }
}

/**
 * @brief       This function servers to config pspi special mode.
 * @param[in]   config  - the pointer of pin special config struct.
 * @return      none
 */
void pspi_master_config_plus(pspi_config_t *config)
{
    spi_set_io_mode(PSPI_MODULE, config->pspi_io_mode);
    spi_set_dummy_cnt(PSPI_MODULE, config->pspi_dummy_cnt);
    if (1 == config->pspi_cmd_en)
    {
        spi_cmd_en(PSPI_MODULE);
    }
    else if (0 == config->pspi_cmd_en)
    {
        spi_cmd_dis(PSPI_MODULE);
    }
}

/**
 * @brief       This function servers to set slave address hspi only.
 * @param[in]   addr - address of slave.
 * @return      none
 */
void hspi_set_address(unsigned int addr)
{
    reg_hspi_addr_32 = addr;
}

/**
 * @brief       This function servers to write hspi fifo.
 * @param[in]   spi_sel - the spi module.
 * @param[in]   data    - the pointer to the data for write.
 * @param[in]   len     - write length.
 * @return      none
 */
void spi_write(spi_sel_e spi_sel, unsigned char *data, unsigned int len)
{
    for (unsigned int i = 0; i < len; i++)
    {
        while (reg_spi_fifo_state(spi_sel) & FLD_SPI_TXF_FULL);
        reg_spi_wr_rd_data(spi_sel, i % 4) = data[i];
    }
}


/**
 * @brief       This function servers to read hspi fifo.
 * @param[in]   spi_sel - the spi module.
 * @param[in]   data    - the pointer to the data for read.
 * @param[in]   len     - read length.
 * @return      none
 */
void spi_read(spi_sel_e spi_sel, unsigned char *data, unsigned int len)
{
    for (unsigned int i = 0; i < len; i++)
    {
        while (reg_spi_fifo_state(spi_sel) & FLD_SPI_RXF_EMPTY);
        data[i] = reg_spi_wr_rd_data(spi_sel, i % 4);
    }
}

/**
 * @brief       This function serves to normal write data in normal.
 * @param[in]   spi_sel - the spi module.
 * @param[in]   data    - the pointer to the data for write.
 * @param[in]   len     - write length.
 * @return      none
 */
void spi_master_write(spi_sel_e spi_sel, unsigned char *data, unsigned int len)
{
    spi_tx_dma_dis(spi_sel);
    spi_tx_fifo_clr(spi_sel);
    spi_tx_cnt(spi_sel, len);
    spi_set_transmode(spi_sel, SPI_MODE_WRITE_ONLY);
    spi_set_cmd(spi_sel, 0x00);//when  cmd  disable that  will not sent cmd,just trigger spi send .
    spi_write(spi_sel, (unsigned char *)data, len);
    while (spi_is_busy(spi_sel));
}

/**
 * @brief       This function serves to normal write and read data.
 * This interface cannot be used for full duplex.
 * rd_len shouldn't set to 0. Must write first, then read.
 * @param[in]   spi_sel - the spi module.
 * @param[in]   wr_data - the pointer to the data for write.
 * @param[in]   wr_len  - write length.
 * @param[in]   rd_data - the pointer to the data for read.
 * @param[in]   rd_len  - read length.
 * @return      none
 */
void spi_master_write_read(spi_sel_e spi_sel, unsigned char *wr_data, unsigned int wr_len, unsigned char *rd_data, unsigned int rd_len)
{
    spi_tx_dma_dis(spi_sel);
    spi_rx_dma_dis(spi_sel);
    spi_tx_fifo_clr(spi_sel);
    spi_rx_fifo_clr(spi_sel);
    spi_tx_cnt(spi_sel, wr_len);
    spi_rx_cnt(spi_sel, rd_len);
    spi_set_transmode(spi_sel, SPI_MODE_WRITE_READ);
    spi_set_cmd(spi_sel, 0x00);//when  cmd  disable that  will not sent cmd,just trigger spi send .
    spi_write(spi_sel, (unsigned char *)wr_data, wr_len);
    spi_read(spi_sel, (unsigned char *)rd_data, rd_len);
    while (spi_is_busy(spi_sel));
}

/**
 * @brief       This function serves to single/dual/quad write to the SPI slave.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   cmd         - cmd one byte will first write.
 * @param[in]   addr        - the address of slave.
 * @param[in]   data        -  pointer to the data need to write.
 * @param[in]   data_len    - length in byte of the data need to write.
 * @param[in]   wr_mode     - write mode.dummy or not dummy.
 * @return      none
 */
void spi_master_write_plus(spi_sel_e spi_sel, unsigned char cmd, unsigned int addr, unsigned char *data, unsigned int data_len, spi_wr_tans_mode_e wr_mode)
{
    spi_tx_dma_dis(spi_sel);
    spi_tx_fifo_clr(spi_sel);
    if (HSPI_MODULE == spi_sel)
    {
     hspi_set_address(addr);
    }
    spi_set_transmode(spi_sel, wr_mode);

    spi_tx_cnt(spi_sel, data_len);
    spi_set_cmd(spi_sel, cmd);
    spi_write(spi_sel, (unsigned char *)data, data_len);
    while (spi_is_busy(spi_sel));
}

/**
 * @brief       This function serves to single/dual/quad  read from the SPI slave.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   cmd         - cmd one byte will first write.
 * @param[in]   addr        - the address of slave.
 * @param[in]   data        - pointer to the data need to read.
 * @param[in]   data_len    - the length of data.
 * @param[in]   rd_mode     - read mode.dummy or not dummy.
 * @return      none
 */
void spi_master_read_plus(spi_sel_e spi_sel, unsigned char cmd, unsigned int addr, unsigned char *data, unsigned int data_len, spi_rd_tans_mode_e rd_mode)
{
    spi_rx_dma_dis(spi_sel);
    spi_rx_fifo_clr(spi_sel);
    if (HSPI_MODULE == spi_sel)
    {
       hspi_set_address(addr);
    }
    spi_set_transmode(spi_sel, rd_mode);
    spi_rx_cnt(spi_sel, data_len);
    spi_set_cmd(spi_sel, cmd);
    spi_read(spi_sel, (unsigned char *)data, data_len);
    while (spi_is_busy(spi_sel));
}

/**
 * @brief       This function serves to write address, then read data from the SPI slave.
 * This interface cannot be used for full duplex.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   cmd         - cmd one byte will first write.
 * @param[in]   addrs       - pointer to the address of slave.
 * @param[in]   addr_len    - the length of address.
 * @param[in]   data        - the pointer to the data for read.
 * @param[in]   data_len    - read length.
 * @param[in]   wr_mode     - write mode.dummy or not dummy.
 * @return      none
 */
void spi_master_write_read_plus(spi_sel_e spi_sel, unsigned char cmd, unsigned char *addrs, unsigned int addr_len, unsigned char *data, unsigned int data_len, spi_rd_tans_mode_e wr_mode)
{
    spi_tx_dma_dis(spi_sel);
    spi_rx_dma_dis(spi_sel);
    spi_tx_fifo_clr(spi_sel);
    spi_rx_fifo_clr(spi_sel);
    spi_tx_cnt(spi_sel, addr_len);
    spi_rx_cnt(spi_sel, data_len);
    spi_set_transmode(spi_sel, wr_mode);

    spi_set_cmd(spi_sel, cmd);
    spi_write(spi_sel, (unsigned char *)addrs, addr_len);
    spi_read(spi_sel, (unsigned char *)data, data_len);
    while (spi_is_busy(spi_sel));
}

/**
 * @brief       This function serves to set tx_dma channel and config dma tx default.
 * @param[in]   chn     - dma channel.
 * @return      none
 * @note        In the case that the DMA transfer is not completed(bit 0 of reg_dma_ctr0(chn): 1-the transmission has not been completed,0-the transmission is completed), re-calling the DMA-related functions may cause problems.
 *              If you must do this, you must perform the following sequence:
 *              1.hspi_reset() 2.spi_tx_fifo_clr(HSPI_MODULE)/spi_rx_fifo_clr(HSPI_MODULE) 3. dma_chn_dis(s_hspi_tx_dma_chn) 4.spi_master_write_dma(HSPI_MODULE)/spi_master_write_read_dma(HSPI_MODULE)/spi_master_write_dma_plus(HSPI_MODULE)/spi_master_write_read_dma_plus(HSPI_MODULE)/spi_set_tx_dma(HSPI_MODULE).
 */
void hspi_set_tx_dma_config(dma_chn_e chn)
{
    s_hspi_tx_dma_chn = chn;
    dma_config(chn, &hspi_tx_dma_config);

}

/**
 * @brief       This function serves to set rx_dma channel and config dma rx default.
 * @param[in]   chn     - dma channel.
 * @return      none
 * @note        In the case that the DMA transfer is not completed(bit 0 of reg_dma_ctr0(chn): 1-the transmission has not been completed,0-the transmission is completed), re-calling the DMA-related functions may cause problems.
 *              If you must do this, you must perform the following sequence:
 *              1.hspi_reset() 2.spi_rx_fifo_clr(HSPI_MODULE)/spi_tx_fifo_clr(HSPI_MODULE) 3. dma_chn_dis(s_hspi_rx_dma_chn) 4.spi_master_write_read_dma(HSPI_MODULE)/spi_master_read_dma_plus(HSPI_MODULE)/spi_master_write_read_dma_plus(HSPI_MODULE)//spi_set_rx_dma(HSPI_MODULE).
 */
void hspi_set_rx_dma_config(dma_chn_e chn)
{
    s_hspi_rx_dma_chn = chn;
    dma_config(chn, &hspi_rx_dma_config);
}

/**
 * @brief       This function serves to set tx_dma channel and config dma tx default.
 * @param[in]   chn     - dma channel.
 * @return      none
 * @note        In the case that the DMA transfer is not completed(bit 0 of reg_dma_ctr0(chn): 1-the transmission has not been completed,0-the transmission is completed), re-calling the DMA-related functions may cause problems.
 *              If you must do this, you must perform the following sequence:
 *              1.pspi_reset() 2.spi_tx_fifo_clr(PSPI_MODULE)/spi_rx_fifo_clr(PSPI_MODULE) 3. dma_chn_dis(s_pspi_tx_dma_chn) 4.spi_master_write_dma(PSPI_MODULE)/spi_master_write_read_dma(PSPI_MODULE)/spi_master_write_dma_plus(PSPI_MODULE)/spi_master_write_read_dma_plus(PSPI_MODULE)/spi_set_tx_dma(PSPI_MODULE).
 */
void pspi_set_tx_dma_config(dma_chn_e chn)
{
    s_pspi_tx_dma_chn = chn;
    dma_config(chn, &pspi_tx_dma_config);

}

/**
 * @brief       This function serves to set rx_dma channel and config dma rx default.
 * @param[in]   chn     - dma channel.
 * @return      none
 * @note        In the case that the DMA transfer is not completed(bit 0 of reg_dma_ctr0(chn): 1-the transmission has not been completed,0-the transmission is completed), re-calling the DMA-related functions may cause problems.
 *              If you must do this, you must perform the following sequence:
 *              1.pspi_reset() 2.spi_rx_fifo_clr(PSPI_MODULE)/spi_tx_fifo_clr(PSPI_MODULE) 3. dma_chn_dis(s_pspi_rx_dma_chn) 4.spi_master_write_read_dma(PSPI_MODULE)/spi_master_read_dma_plus(PSPI_MODULE)/spi_master_write_read_dma_plus(PSPI_MODULE)/spi_set_rx_dma(PSPI_MODULE).
 */
void pspi_set_rx_dma_config(dma_chn_e chn)
{
    s_pspi_rx_dma_chn = chn;
    dma_config(chn, &pspi_rx_dma_config);
}

/**
 * @brief       this  function set spi dma channel.
 * @param[in]   spi_dma_chn - dma channel.
 * @param[in]   src_addr    - the address of source.
 * @param[in]   dst_addr    - the address of destination.
 * @param[in]   len         - the length of data.
 * */
void spi_set_dma(dma_chn_e spi_dma_chn, unsigned int src_addr, unsigned int dst_addr, unsigned int len)
{
    dma_set_address(spi_dma_chn, src_addr, dst_addr);
    dma_set_size(spi_dma_chn, len, DMA_WORD_WIDTH);
    dma_chn_en(spi_dma_chn);
}

/**
 * @brief       this  function set spi tx dma channel.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   src_addr    - the address of source.
 * @param[in]   len         - the length of data.
 * @return      none
 * @note        src_addr : must be aligned by word (4 bytes), otherwise the program will enter an exception.
 * */
void spi_set_tx_dma(spi_sel_e spi_sel, unsigned char* src_addr,unsigned int len)
{
    unsigned char tx_dma_chn;
    if (HSPI_MODULE == spi_sel)
    {
        tx_dma_chn = s_hspi_tx_dma_chn;
    }
    else
    {
        tx_dma_chn = s_pspi_tx_dma_chn;
    }
    spi_rx_tx_irq_trig_cnt(spi_sel, 4);//recover trigger level to 4.
    spi_tx_cnt(spi_sel,len);
    /**
     * Move spi_tx_dma_en() from the initialization enable in demo to here.
     */
    spi_tx_dma_en(spi_sel);
    dma_set_address(tx_dma_chn, (unsigned int)src_addr , reg_spi_data_buf_adr(spi_sel));
    dma_set_size(tx_dma_chn, len, DMA_WORD_WIDTH);
    dma_chn_en(tx_dma_chn);
}


/**
 * @brief      this  function set spi rx dma channel.
 * @param[in]  spi_sel  - the spi module.
 * @param[in]  dst_addr - This parameter is the first address of the received data buffer, which must be 4 bytes aligned, otherwise the program will enter an exception.
 *                        and the actual buffer size defined by the user needs to be not smaller than the len, otherwise there may be an out-of-bounds problem.
 * @param[in]  len      - This parameter is used to set the size of the received dma and must be set to a multiple of 4. The maximum value that can be set is 0xFFFFFC.
 * @return     none
 */
void spi_set_rx_dma(spi_sel_e spi_sel, unsigned char* dst_addr,unsigned int len)
{
    unsigned char rx_dma_chn;
    if (HSPI_MODULE == spi_sel)
    {
        rx_dma_chn = s_hspi_rx_dma_chn;
    }
    else
    {
        rx_dma_chn = s_pspi_rx_dma_chn;
    }
    spi_rx_tx_irq_trig_cnt(spi_sel, 5);//setting only for fixing the bug that slave receive number of bytes in multiples of 4 will fail.
    /**
     * Move spi_rx_dma_en() from the initialization enable in demo to here.
     */
    spi_rx_dma_en(spi_sel);
    dma_set_address(rx_dma_chn, reg_spi_data_buf_adr(spi_sel), (unsigned int)dst_addr);
    dma_set_size(rx_dma_chn, len, DMA_WORD_WIDTH);
    dma_chn_en(rx_dma_chn);
}


/**
 * @brief       This function serves to normal write data by dma.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   src_addr    - the pointer to the data for write.
 * @param[in]   len         - write length.
 * @return      none
 * @note        src_addr : must be aligned by word (4 bytes), otherwise the program will enter an exception.
 */
void spi_master_write_dma(spi_sel_e spi_sel, unsigned char *src_addr, unsigned int len)
{
    unsigned char tx_dma_chn;
    spi_tx_fifo_clr(spi_sel);
    spi_tx_dma_en(spi_sel);
    spi_tx_cnt(spi_sel, len);
    spi_set_transmode(spi_sel, SPI_MODE_WRITE_ONLY);
    if (HSPI_MODULE == spi_sel)
    {
        tx_dma_chn = s_hspi_tx_dma_chn;
    }
    else
    {
        tx_dma_chn = s_pspi_tx_dma_chn;
    }
    spi_set_dma(tx_dma_chn,(unsigned int)src_addr, reg_spi_data_buf_adr(spi_sel), len);
    spi_set_cmd(spi_sel, 0x00);
}

/**
 * @brief       This function serves to normal write cmd and address, then read data by dma.
 * The interface does not support full-duplex communication.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   addr        - the pointer to the cmd and address for write.
 * @param[in]   addr_len    - write length.
 * @param[in]   data        - the pointer to the data for read.
 * @param[in]   data_len    - read length.
 * @return      none
 * @note        addr/data : must be aligned by word (4 bytes), otherwise the program will enter an exception.
 */
void spi_master_write_read_dma(spi_sel_e spi_sel, unsigned char *addr, unsigned int addr_len, unsigned char *data, unsigned int data_len)
{
    unsigned char tx_dma_chn, rx_dma_chn;
    spi_tx_fifo_clr(spi_sel);
    spi_rx_fifo_clr(spi_sel);
    spi_tx_dma_en(spi_sel);
    spi_rx_dma_en(spi_sel);
    spi_tx_cnt(spi_sel, addr_len);
    spi_rx_cnt(spi_sel, data_len);
    spi_set_transmode(spi_sel, SPI_MODE_WRITE_READ);
    if (HSPI_MODULE == spi_sel)
    {
        tx_dma_chn = s_hspi_tx_dma_chn;
        rx_dma_chn = s_hspi_rx_dma_chn;
    }
    else
    {
        tx_dma_chn = s_pspi_tx_dma_chn;
        rx_dma_chn = s_pspi_rx_dma_chn;
    }
    spi_set_dma(tx_dma_chn, (unsigned int)addr, reg_spi_data_buf_adr(spi_sel), addr_len);
    spi_set_dma(rx_dma_chn, reg_spi_data_buf_adr(spi_sel), (unsigned int)data, data_len);
    spi_set_cmd(spi_sel, 0x00);//when  cmd  disable that  will not sent cmd,just trigger spi send .
}

/**
 * @brief       This function serves to single/dual/quad  write to the SPI slave by dma.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   cmd         - cmd one byte will first write.
 * @param[in]   addr        - the address of slave.
 * @param[in]   data        - pointer to the data need to write.
 * @param[in]   data_len    - length in byte of the data need to write.
 * @param[in]   wr_mode     - write mode.dummy or not dummy.
 * @return      none
 * @note        data : must be aligned by word (4 bytes), otherwise the program will enter an exception.
 */
void spi_master_write_dma_plus(spi_sel_e spi_sel, unsigned char cmd, unsigned int addr, unsigned char *data, unsigned int data_len, spi_wr_tans_mode_e wr_mode)
{
    unsigned char tx_dma_chn;
    spi_tx_fifo_clr(spi_sel);
    spi_tx_dma_en(spi_sel);
    spi_tx_cnt(spi_sel, data_len);
    spi_set_transmode(spi_sel, wr_mode);
    if (HSPI_MODULE == spi_sel)
    {
        tx_dma_chn = s_hspi_tx_dma_chn;
        hspi_set_address(addr);
    }
    else
    {
        tx_dma_chn = s_pspi_tx_dma_chn;
    }
    spi_set_dma(tx_dma_chn, (unsigned int)data, reg_spi_data_buf_adr(spi_sel), data_len);
    spi_set_cmd(spi_sel, cmd);
}

/**
 * @brief       This function serves to single/dual/quad  read from the SPI slave by dma.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   cmd         - cmd one byte will first write.
 * @param[in]   addr        - the address of slave.
 * @param[in]   dst_addr    - pointer to the buffer that will cache the reading out data.
 * @param[in]   data_len    - length in byte of the data need to read.
 * @param[in]   rd_mode     - read mode.dummy or not dummy.
 * @return      none
 * @note        dst_addr : must be aligned by word (4 bytes), otherwise the program will enter an exception.
 */
void spi_master_read_dma_plus(spi_sel_e spi_sel, unsigned char cmd, unsigned int addr, unsigned char *dst_addr, unsigned int data_len, spi_rd_tans_mode_e rd_mode)
{
    unsigned char rx_dma_chn;
    spi_rx_fifo_clr(spi_sel);
    spi_rx_dma_en(spi_sel);
    spi_set_transmode(spi_sel, rd_mode);
    spi_rx_cnt(spi_sel, data_len);
    if (HSPI_MODULE == spi_sel)
    {
        rx_dma_chn = s_hspi_rx_dma_chn;
        hspi_set_address(addr);
    }
    else
    {
        rx_dma_chn = s_pspi_rx_dma_chn;
    }
    spi_set_dma(rx_dma_chn, reg_spi_data_buf_adr(spi_sel), (unsigned int)dst_addr, data_len);
    spi_set_cmd(spi_sel, cmd);
}

/**
 * @brief       This function serves to single/dual/quad write address and read from the SPI slave by dma.
 * This interface could be used for full duplex.
 * When this interface is used for full-duplex communication, it can only be used on the master side.
 * 1.the spi_master_config() interface must be called first to disable hardware cmd and hardware address
 * 2.must cmd is 0,addr_len is equal to rd_len,rd_mode is SPI_MODE_WRITE_AND_READ.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   cmd         - cmd one byte will first write.
 * @param[in]   addr        - the pointer to the cmd and address for write.
 * @param[in]   addr_len    - the length of address.
 * @param[in]   rd_data     - pointer to the buffer that will cache the reading out data.
 * @param[in]   rd_len      - length in byte of the data need to read.
 * @param[in]   rd_mode     - read mode.dummy or not dummy.
 * @return      none
 * @note        addr/rd_data : must be aligned by word (4 bytes), otherwise the program will enter an exception.
 */
void spi_master_write_read_dma_plus(spi_sel_e spi_sel, unsigned char cmd, unsigned char *addr, unsigned int addr_len, unsigned char *rd_data, unsigned int rd_len, spi_rd_tans_mode_e rd_mode)
{
    unsigned char tx_dma_chn, rx_dma_chn;
    spi_tx_fifo_clr(spi_sel);
    spi_rx_fifo_clr(spi_sel);
    spi_tx_dma_en(spi_sel);
    spi_rx_dma_en(spi_sel);
    spi_tx_cnt(spi_sel,addr_len);
    spi_rx_cnt(spi_sel, rd_len);
    spi_set_transmode(spi_sel, rd_mode);
    if (HSPI_MODULE == spi_sel)
    {
        tx_dma_chn = s_hspi_tx_dma_chn;
        rx_dma_chn = s_hspi_rx_dma_chn;
    }
    else
    {
        tx_dma_chn = s_pspi_tx_dma_chn;
        rx_dma_chn = s_pspi_rx_dma_chn;
    }
    spi_set_dma(tx_dma_chn, (unsigned int)addr, reg_spi_data_buf_adr(spi_sel), addr_len);
    spi_set_dma(rx_dma_chn, reg_spi_data_buf_adr(spi_sel), (unsigned int)rd_data, rd_len);
    spi_set_cmd(spi_sel, cmd);//when  cmd  disable that  will not sent cmd,just trigger spi send .
}

/**
 * @brief       This function serves to single/dual (quad) write to the SPI slave by xip.
 * @param[in]   cmd         - cmd one byte will first write.
 * @param[in]   addr_offset - offset of xip base address.
 * @param[in]   data        - pointer to the data need to write.
 * @param[in]   data_len    - length in byte of the data need to write.
 * @param[in]   wr_mode     - write mode  dummy or not dummy.
 * @return      none
 */
_attribute_ram_code_sec_noinline_ void hspi_master_write_xip(unsigned char cmd, unsigned int addr_offset, unsigned char *data, unsigned int data_len, spi_wr_tans_mode_e wr_mode)
{
    hspi_xip_write_transmode(wr_mode);
    hspi_xip_addr_offset(addr_offset);
    hspi_xip_set_wr_cmd(cmd);
    for (unsigned int i = 0; i < data_len; i++)
    {
        write_reg8(reg_hspi_xip_base_adr + i, data[i]);
    }
}

/**
 * @brief       This function serves to single/dual (quad) read from the SPI slave by xip.
 * @param[in]   cmd         - cmd one byte will first write.
 * @param[in]   addr_offset - offset of xip base address.
 * @param[in]   data        - pointer to the data need to read.
 * @param[in]   data_len    - length in byte of the data need to read.
 * @param[in]   rd_mode     - read mode.dummy or not dummy.
 * @return      none
 */
_attribute_ram_code_sec_noinline_ void hspi_master_read_xip(unsigned char cmd, unsigned int addr_offset, unsigned char *data, unsigned int data_len, spi_rd_tans_mode_e rd_mode)
{
    hspi_xip_read_transmode(rd_mode);
    hspi_xip_addr_offset(addr_offset);
    hspi_xip_set_rd_cmd(cmd);

    for (unsigned int i = 0; i < data_len; i++)
    {
        data[i] = read_reg8(reg_hspi_xip_base_adr + i);
    }
}

/**
 * @brief       This function serves to a cmd and one data write to the SPI slave by xip.
 * @param[in]   cmd         - cmd one byte will first write.
 * @param[in]   addr_offset - offset of xip base address.
 * @param[in]   data_in     - data need to write.
 * @param[in]   wr_mode     - write mode  dummy or not dummy.
 * @return      none
 */
void hspi_master_write_xip_cmd_data(unsigned char cmd, unsigned int addr_offset, unsigned char data_in, spi_wr_tans_mode_e wr_mode)
{
    hspi_xip_write_transmode(wr_mode);
    hspi_xip_addr_offset(addr_offset);
    hspi_xip_set_wr_cmd(cmd);
    write_reg8(reg_hspi_xip_base_adr, data_in);
}

/**
 * @brief       This function serves to write and read data simultaneously.
 * This interface can only be used by the master.
 * When initializing the master, call the spi_master_config() interface to disable the hardware cmd and hardware address,
 * and then start sending and receiving data.
 * The default chunk size sent and received by this interface is 8byte.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   write_data  - write data pointer.
 * @param[in]   read_data   - read data pointer.
 * @param[in]   len         - write/read length.
 * @return      none
 */
void spi_master_write_read_full_duplex(spi_sel_e spi_sel,unsigned char *write_data, unsigned char *read_data, unsigned int len)
{
    unsigned int spi_wr_rd_size = 8;
    unsigned int chunk_size = spi_wr_rd_size;
    spi_tx_dma_dis(spi_sel);
    spi_rx_dma_dis(spi_sel);
    spi_set_transmode(spi_sel, SPI_MODE_WRITE_AND_READ);
    spi_set_cmd(spi_sel, 0);
    spi_tx_fifo_clr(spi_sel);
    spi_rx_fifo_clr(spi_sel);
    spi_tx_cnt(spi_sel, len);
    spi_rx_cnt(spi_sel, len);

    for(unsigned int i = 0; i<len; i = i +chunk_size){
        if(chunk_size > (len - i)){
            chunk_size = len - i;
        }
        spi_write(spi_sel, write_data + i, chunk_size);
        if(len < spi_wr_rd_size){
            spi_read(spi_sel, read_data, chunk_size);
        }
        else if(i == 0){
            spi_read(spi_sel, read_data, chunk_size - 1);
        }else if((len - i) > spi_wr_rd_size){
            spi_read(spi_sel, read_data + i -1, chunk_size);
        }else{
            spi_read(spi_sel, read_data + i -1, chunk_size + 1);
        }
        spi_rx_fifo_clr(spi_sel);
        spi_tx_fifo_clr(spi_sel);
    }
    while(spi_is_busy(spi_sel));
}
/**
 * @brief       This function serves to read data in normal.
 * @param[in]   spi_sel     - the spi module.
 * @param[in]   data        - read data pointer.
 * @param[in]   len         - read length.
 * @return      none
 */
void spi_master_read(spi_sel_e spi_sel, unsigned char *data, unsigned int len)
{
    spi_rx_dma_dis(spi_sel);
    spi_rx_fifo_clr(spi_sel);
    spi_rx_cnt(spi_sel, len);
    spi_set_transmode(spi_sel, SPI_MODE_READ_ONLY);
    spi_set_cmd(spi_sel, 0x00);//when  cmd  disable that  will not sent cmd,just trigger spi send .
    spi_read(spi_sel, (unsigned char *)data, len);
    while (spi_is_busy(spi_sel));
}
